生命週期在大部分的軟體開發中都會了解這個名詞,簡單來說就是某個東西從出現到消失,中間的每個階段都會有一個對應的狀態,那為什麼要有狀態?這些狀態都是為了讓開發者在特定情境下去針對應用程式、物件、UI 或是進行合適的操作,例如:在一開始的時候,我們需要將某些服務啟動或是給予屬性初始值,準備待會使用; 在初始化後的第二階段,可以開始進行資料的操作,可能是請求資料,也可能是數據處理; 最後在被銷毀,準備死亡的階段,可以進行資源的釋放,防止記憶體洩漏,讓整體效能提升。開發者在對的時間做對的事,告訴 APP 該怎麼運行和顯示,確保使用者擁有好的體驗,以及 APP 能流暢的呈現,這些都是我們的職責。所以生命週期對於 Mobile 開發來說非常重要,是不可忽視的一環。
大家都知道 Flutter 中有 StatelessWidget 和 StatefulWidget,而 StatefulWidget 因為需要長期保持狀態,會需要透過 State 去維護,它本身是託管在 Element 底下,也因為成本高的關係不適合重複創建。Flutter Framework 讓 Element 以及 State 可以在不同情境下觸發一些介面,讓我們能即時針對當前的 Widget 或是資源去進行處理,而在 State 中就有比較多環節我們需要注意,以下就跟著我來了解它們。
createElement()
,創建 StatefulElement 同時注入 WidgetcreateState()
創建新的 State,並由 Element 記錄下來created
super.initState()
,接著 StateLifecycle 會轉變為 initialized
,並呼叫 didChangeDependencies()
didChangeDependencies()
、build()
等等其他位置State 生命週期包含 4 個部分,包含 created
、initialized
、ready
、defunct
。
initialized
markNeedsBuild()
,也就是我們使用 setState()
做的事情,進行多層的生命週期與狀態檢查後,標記此元件對應的 element 為 dirty,並添加到 _dirtyElements 這個清單,等候待會下一幀進行 rebuildcreateElement()
的時候被 mount
綁定到樹上,這時候就會調用didChangeDependencies()
方法將會被調用didUpdateWidget()
中取消 Old Widget 訂閱的 callback,並讓 New Widgets 訂閱 callbackcanUpdate()
來檢查 Widget Tree 中同一位置的新舊節點,決定是否要更新,如果返回 true,代表新舊元件的 key
和 runtimeType
都相同,就會觸發didUpdateWidget()
。使用新 Widget 配置更新原本的 Element 配置; 反之如果返回 false 則創建新的 Elementressemble()
後會觸發 didUpdateWidget()
setState()
或其他會執行 build()
的操作,父元件不會觸發 didUpdateWidget(),而子元件 didUpdateWidget()
會被觸發setState()
在這使用會沒有作用,因為 didUpdateWidget() 執行完後就會執行 rebuild()
,也就是 State 的 build()
reassemble()
didUpdateWidget()
build()
針對當前元件執行 markNeedsBuild()
,觸發 build()
進行 rebuild,接著同時針對所有的 child 都執行 reassemble()
。
ready
didChangeDependencies()
、didUpdateWidget()
,或是我們手動執行 setState()
,都會觸發它底層是 Element 會執行 performRebuild()
,接著觸發 StatelessWidget-build() 或是 State.build() 創建 Widget Tree,並將 element 本身的 dirty 屬性設為 false,代表乾淨了、更新完成,最後在使用 updateChild()
進行 child 刷新。
Widget Tree
中被移出後呼叫,移出後會等待重新添加到 Widget Tree
,可能會在當前幀更改完成之前重新插入樹中,如果未被插入到其他節點時,則會繼續執行 dispose()
看 Code 部分,同時在這裡也會將此元件原本有依賴的 InheritedWidget 都拿出來,以迴圈的方式,將此 Widget 從他們的依賴者名單中移除,也就代表之後的更新不會再通知我了。最後將 Element 生命週期設為 inactive。
defunct
context.mounted
屬性會被設置為false,也代表生命週期的結束,所以不能在此執行 setState()
super.dispose()
應該作為 Widget-dispose 的最後一個執行函式,資源釋放需要在之前執行,如果在後面則不會處理元件的 Element 會執行 unmount(),從樹上拔除,並將元件和相關依賴資源釋放,然後生命週期從 inactive 轉變為 defunct,這個時候 State 生命週期也會轉變為 defunct 狀態。
BuildContext
,它也是 Element 的介面,實際上就是 Element,也代表這個 Widget 在樹中的位置setState()
,或是其他 context 操作可以看到源碼,很簡單的就是檢查 element 是否為 null。可以由註解得知,同時也是在檢查 State 物件是否還存在。最後一行也提到,避免在 mounted
為 false 的時候執行 setState()
,不然就會報錯。
如果專案有使用 flutter_lints 套件保護代碼品質的話,它本身也有提供相對的規則來幫我們檢查是否有在非同步操作後直接使用 context。也提醒大家 Linting Tool 代碼分析是很重要的哦。
在 Flutter 中關於生命週期這件事,Element 是核心角色,它掌管著 Widget 和 State、RenderObject 更新,建議開發者可以去了解他們,或是跟著我解析 Source Code,當我們越熟悉也代表越了解 Flutter 以及 APP 是如何運作,開發過程中會更有感覺喔!
Day 6: 完全掌握 Flutter APP 生命週期,跟著我從源碼認識它!